/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.util.core;

import com.blandware.atleap.webapp.menu.MenuComponent;
import com.opensymphony.oscache.base.Cache;
import com.opensymphony.oscache.base.NeedsRefreshException;
import com.opensymphony.oscache.web.ServletCacheAdministrator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.PageContext;
import java.io.Serializable;
import java.util.Date;

/**
 * <p>This is a helper to use OSCache</p>
 * <p><a href="CacheUtil.java.html"><i>View Source</i></a></p>
 * <p/>
 *
 * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.26 $ $Date: 2007/12/19 21:09:03 $
 */
public class CacheUtil {
	protected transient final Log log = LogFactory.getLog(CacheUtil.class);

    // identifier suffixes
    public static final String PAGE_CFV_SUFFIX = "_pageCFV";
	public static final String LAYOUT_CFV_SUFFIX = "_layoutCFV";
	public static final String LOCALIZABLE_CFV_SUFFIX = "_localizable_CFV";
    public static final String PAGE_SEQUENCE_SUFFIX = "_pageSequence";
    public static final String LAYOUT_SEQUENCE_SUFFIX = "_layoutSequence";
    public static final String RESOURCE_SUFFIX = "_resource";
	public static final String CONTENT_PAGE_SUFFIX = "_contentPage";
	public static final String MENU_SUFFIX = "_menu";
	public static final String FIELD_INDICES_SUFFIX = "_fieldIndices";

    // group suffixes
    public static final String PAGE_CFV_GROUP_SUFFIX = "_pageCFVGroup";
	public static final String LAYOUT_CFV_GROUP_SUFFIX = "_layoutCFVGroup";
	public static final String LOCALIZABLE_CFV_GROUP_SUFFIX = "_localizable_CFVGroup";
    public static final String PAGE_SEQUENCE_GROUP_SUFFIX = "_pageSequenceGroup";
    public static final String LAYOUT_SEQUENCE_GROUP_SUFFIX = "_layoutSequenceGroup";

    public static final String FIELD_VALUE_GROUP = "fieldValueGroup";
    public static final String SEQUENCE_GROUP = "sequenceGroup";
    public static final String FIELD_INDEXES_GROUP = "fieldIndexesGroup";
	public static final String RESOURCE_GROUP = "resourceGroup";
	public static final String CONTENT_PAGE_GROUP = "contentPageGroup";
	public static final String MENU_GROUP = "menuGroup";

	public static final String INSTANCE_KEY = "com.blandware.atleap.util.CacheUtil.INSTANCE";

	protected static final String TIME_KEY = "cache.web.time";
	protected static final String RESOURCE_MAXSIZE_KEY = "cache.web.resource.maxsize";

    protected static final String DUMMY_LANGUAGE = "en";

    protected ServletCacheAdministrator admin = null;
	protected HttpServletRequest request = null;
	protected ServletContext servletContext = null;
	protected int refreshPeriod = -1;
	protected int resourceMaxSize = 50000;

	/**
	 * Returns instance of CacheUtil
	 *
	 * @param request Http request from which to obtain instance
	 * @return Instance of CacheUtil
	 */
	public static CacheUtil getInstance(HttpServletRequest request) {
		CacheUtil ourInstance = (CacheUtil) request.getAttribute(INSTANCE_KEY);
		if ( ourInstance == null ) {
			ourInstance = new CacheUtil(request);
			request.setAttribute(INSTANCE_KEY, ourInstance);
		}
		return ourInstance;
	}

	/**
	 * Constructs a new instance of CacheUtil
     *
     * @param request Http request to which this instance will be tied
	 */
	protected CacheUtil(HttpServletRequest request) {
		this.request = request;
		this.servletContext = request.getSession().getServletContext();

		//cache administrator
		admin = ServletCacheAdministrator.getInstance(servletContext);
		if ( admin == null ) {
			String error = "Cannot get cache administrator";
			if ( log.isErrorEnabled() ) {
				log.error(error);
			}
			throw new RuntimeException(error);
		}

		//refresh period
		Integer rp = GlobalProperties.getInstance(servletContext).getInteger(TIME_KEY);
		if ( rp != null ) {
			refreshPeriod = rp.intValue();
		} else {
			if ( log.isWarnEnabled() ) {
				log.warn("Cache refresh period is not specified in key '" + TIME_KEY + "' using default " + String.valueOf(refreshPeriod));
			}
		}

		//resource max size
		Integer ms = GlobalProperties.getInstance(servletContext).getInteger(RESOURCE_MAXSIZE_KEY);
		if ( ms != null ) {
			resourceMaxSize = ms.intValue();
		} else {
			if ( log.isWarnEnabled() ) {
				log.warn("Maxsize of resource in cache is not specified in key '" + RESOURCE_MAXSIZE_KEY + "' using default " + String.valueOf(resourceMaxSize));
			}
		}

	}

	// P A G E   F I E L D   V A L U E

	/**
	 * Gets content from the cache for Page from Application Scope
	 *
	 * @param pageUri    Uri of the Page
	 * @param identifier Identifier of the ContentField
	 * @param locale     Locale string
	 * @return Data object with content or <code>null</code> if content is not found or stale.
	 */
	public CFVData getPageFieldValueFromCache(String pageUri, String identifier, String locale) {
		return (CFVData) getFromCache(pageUri + "_" + identifier, locale,
                PAGE_CFV_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts content into cache into Application Scope for Page
	 *
	 * @param data       Data to put into cache
	 * @param pageUri    Uri of the page
	 * @param identifier Identifier of the content field
	 * @param locale     Locale string
	 */
	public void putPageFieldValueInCache(CFVData data, String pageUri, String identifier, String locale) {
		putInCache(data, pageUri + "_" + identifier, locale, PAGE_CFV_SUFFIX,
                new String[]{pageUri + PAGE_CFV_GROUP_SUFFIX, FIELD_VALUE_GROUP},
                PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes content from the cache for Page from Application Scope
	 *
	 * @param pageUri    Uri of the Page
	 * @param identifier Identifier of the ContentField
	 * @param locale     Locale string
	 */
	public void flushPageFieldValueCache(String pageUri, String identifier, String locale) {
		flushCache(pageUri + "_" + identifier, locale,
                PAGE_CFV_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes content from the cache for all fields of page
	 *
	 * @param pageUri Uri of the Page
	 */
	public void flushPageFieldValueCache(String pageUri) {
		flushCacheGroup(pageUri + PAGE_CFV_GROUP_SUFFIX,
                PageContext.APPLICATION_SCOPE);
	}


	// L A Y O U T   F I E L D   V A L U E

	/**
	 * Puts into cache into Application Scope for layout. This is different from
     * <code>putPageFieldValueInCache()</code> because this method also adds
     * the given data to group corresponding to layout definition, not only
     * to group for page URI.
	 *
	 * @param data             Data to put into cache
	 * @param layoutDefinition Definition name of layout
     * @param pageUri          Uri of the page
	 * @param identifier       Identifier of the content field
	 * @param locale           Locale string
	 */
	public void putLayoutFieldValueInCache(CFVData data, String layoutDefinition, String pageUri, String identifier, String locale) {
		putInCache(data, pageUri + "_" + identifier, locale,PAGE_CFV_SUFFIX,
                new String[]{pageUri + PAGE_CFV_GROUP_SUFFIX,
                        layoutDefinition + LAYOUT_CFV_GROUP_SUFFIX,
                        FIELD_VALUE_GROUP},
                PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes content from the cache for all fields of layout
	 *
	 * @param layoutDefinition definition of the Layout
	 */
	public void flushLayoutFieldValueCache(String layoutDefinition) {
		flushCacheGroup(layoutDefinition + LAYOUT_CFV_GROUP_SUFFIX,
                PageContext.APPLICATION_SCOPE);
	}

	// L O C A L I Z A B L E   F I E L D   V A L U E

	/**
	 * Gets content from the cache for undefined localizable from Application Scope
	 *
	 * @param localizableId ID of localizable
	 * @param identifier    Identifier of the content field
	 * @param locale        Locale string
	 * @return Data object with content or <code>null</code> if content is not found or stale.
	 */
	public CFVData getLocalizableFieldValueFromCache(Long localizableId, String identifier, String locale) {
		return (CFVData) getFromCache(localizableId + "_" + identifier, locale,
                LOCALIZABLE_CFV_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts into cache into Application Scope for localizable
	 *
	 * @param data          data to put into cache
	 * @param localizableId ID of localizable
	 * @param identifier    Identifier of the content field
	 * @param locale        locale string
	 */
	public void putLocalizableFieldValueInCache(CFVData data, Long localizableId, String identifier, String locale) {
		putInCache(data, localizableId + "_" + identifier, locale,
                LOCALIZABLE_CFV_SUFFIX,
                new String[]{localizableId.toString() + LOCALIZABLE_CFV_GROUP_SUFFIX,
                        FIELD_VALUE_GROUP},
                PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes content from the cache for localizable from Application Scope
	 *
	 * @param localizableId ID of localizable
	 * @param identifier    Identifier of the content field
	 * @param locale        Locale string
	 */
	public void flushLocalizableFieldValueCache(Long localizableId, String identifier, String locale) {
		flushCache(localizableId + "_" + identifier, locale,
                LOCALIZABLE_CFV_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes content from the cache for all fields of localizable
	 *
	 * @param localizableId ID of localizable
	 */
	public void flushLocalizableFieldValueCache(Long localizableId) {
		flushCacheGroup(localizableId.toString() + LOCALIZABLE_CFV_GROUP_SUFFIX,
                PageContext.APPLICATION_SCOPE);
	}

    
    // P A G E   S E Q U E N C E

	/**
	 * Gets sequence from the cache for Page from Application Scope
	 *
	 * @param pageUri    Uri of the Page
	 * @param identifier Identifier of the sequence
	 * @return Data object with sequence data or <code>null</code> if sequence is not found or stale.
	 */
	public SequenceData getPageSequenceFromCache(String pageUri, String identifier) {
		return (SequenceData) getFromCache(pageUri + "_" + identifier, DUMMY_LANGUAGE,
                PAGE_SEQUENCE_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts sequence into cache into Application Scope for Page
	 *
	 * @param data       Data to put into cache
	 * @param pageUri    Uri of the page
	 * @param identifier Identifier of the sequence
	 */
	public void putPageSequenceInCache(SequenceData data, String pageUri, String identifier) {
		putInCache(data, pageUri + "_" + identifier, DUMMY_LANGUAGE, PAGE_SEQUENCE_SUFFIX,
                new String[]{pageUri + PAGE_SEQUENCE_GROUP_SUFFIX, SEQUENCE_GROUP},
                PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes sequence from the cache for Page from Application Scope
	 *
	 * @param pageUri    Uri of the Page
	 * @param identifier Identifier of the sequence
	 */
	public void flushPageSequenceCache(String pageUri, String identifier) {
		flushCache(pageUri + "_" + identifier, DUMMY_LANGUAGE,
                PAGE_SEQUENCE_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes cache for all sequences of page
	 *
	 * @param pageUri Uri of the Page
	 */
	public void flushPageSequenceCache(String pageUri) {
		flushCacheGroup(pageUri + PAGE_SEQUENCE_GROUP_SUFFIX,
                PageContext.APPLICATION_SCOPE);
	}

    
    // L A Y O U T   S E Q U E N C E

	/**
	 * Puts sequence into cache into Application Scope for layout. This is different from
     * <code>putPageSequenceInCache()</code> because this method also adds
     * the given data to group corresponding to layout definition, not only
     * to group for page URI.
	 *
	 * @param data             Data to put into cache
	 * @param layoutDefinition Definition name of layout
     * @param pageUri          Uri of the page
	 * @param identifier       Identifier of the sequence
	 */
	public void putLayoutSequenceInCache(SequenceData data, String layoutDefinition, String pageUri, String identifier) {
		putInCache(data, pageUri + "_" + identifier, DUMMY_LANGUAGE, PAGE_SEQUENCE_SUFFIX,
                new String[]{pageUri + PAGE_SEQUENCE_GROUP_SUFFIX,
                        layoutDefinition + LAYOUT_SEQUENCE_GROUP_SUFFIX,
                        SEQUENCE_GROUP},
                PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes cache for all sequences of layout
	 *
	 * @param layoutDefinition definition of the Layout
	 */
	public void flushLayoutSequenceCache(String layoutDefinition) {
		flushCacheGroup(layoutDefinition + LAYOUT_SEQUENCE_GROUP_SUFFIX,
                PageContext.APPLICATION_SCOPE);
	}

    // R E S O U R C E

	/**
	 * Gets resource data from cache
	 *
	 * @param uri The uri of resource to get from cache
	 * @return data of resource and its mime type
	 */
	public ResourceData getResourceFromCache(String uri) {
		return (ResourceData) getFromCache(uri, null, RESOURCE_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts the resource into cache
	 *
	 * @param resourceData Data of resource
	 * @param uri The uri of resource
	 */
	public void putResourceInCache(ResourceData resourceData, String uri) {
		if ( resourceData.getData().length <= resourceMaxSize ) {
			putInCache(resourceData, uri, null, RESOURCE_SUFFIX, new String[]{RESOURCE_GROUP}, PageContext.APPLICATION_SCOPE);
		} else {
			//put without data
			resourceData.setData(null);
			putInCache(resourceData, uri, null, RESOURCE_SUFFIX, new String[]{RESOURCE_GROUP}, PageContext.APPLICATION_SCOPE);
		}
	}

    /**
     * Update roles of resource in cache
     * @param uri uri of resource
     * @param roles new roles
     */
    public void updateResourceRolesInCache(String uri, String roles) {
        ResourceData resourceData = getResourceFromCache(uri);
        if (resourceData != null) {
            resourceData.setRoles(roles);
            putResourceInCache(resourceData, uri);
        }
    }

	/**
	 * Flushes resource from the cache
	 *
	 * @param uri The uri of resource to flush
	 */
	public void flushResourceCache(String uri) {
		flushCache(uri, null, RESOURCE_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

    /**
     * Flushes all resources from cache
     */
    public void flushResourceCache() {
        flushCacheGroup(RESOURCE_GROUP, PageContext.APPLICATION_SCOPE);
    }

	// M E N U

	/**
	 * Gets menu component from cache.
	 *
	 * @param menuName         Name of menu component
	 * @param locale           Locale string
	 * @param layoutDefinition Definition of layout to which this menu component
     *                         belongs
	 * @param pageUri          Uri of page to which this menu component belongs
	 * @return MenuComponent or <code>null</code> if nothing has been found or cache is stale.
	 */
	public MenuComponent getMenuFromCache(String menuName, String locale, String layoutDefinition, String pageUri) {
		return (MenuComponent) getFromCache(menuName + "_" + layoutDefinition + "_" + pageUri, locale, MENU_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts menu component into cache.
	 *
	 * @param menuComponent    Menu component to put into cache
	 * @param menuName         Name of menu component
	 * @param locale           Locale string
	 * @param layoutDefinition Definition of layout to which this menu component
     *                         belongs
	 * @param pageUri          Uri of page to which this menu component
     *                         belongs
	 */
	public void putMenuInCache(MenuComponent menuComponent, String menuName, String locale, String layoutDefinition, String pageUri) {
		putInCache(menuComponent, menuName + "_" + layoutDefinition + "_" + pageUri, locale, MENU_SUFFIX, new String[]{MENU_GROUP}, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes all menus from a cache
	 */
	public void flushMenuCache() {
		flushCacheGroup(MENU_GROUP, PageContext.APPLICATION_SCOPE);
	}


	// F I E L D   I N D I C E S

	/**
	 * Gets an array of content field indices for indexed field
	 *
	 * @param fieldIdentifier  Identifier of indexed field
	 * @param locale           Locale string
	 * @param layoutDefinition Definition of layout to which a field belongs
	 * @param pageUri          Uri of page to which a field belongs
	 * @return Array of indices or <code>null</code> if not found or stale.
	 */
	public String[] getFieldIndices(String fieldIdentifier, String locale, String layoutDefinition, String pageUri) {
		return (String[]) getFromCache(fieldIdentifier + "_" + layoutDefinition + "_" + pageUri, locale, FIELD_INDICES_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts array of field indices into cache
	 *
	 * @param fieldIndices     Array of field indices
	 * @param fieldIdentifier  Identifier of field
	 * @param locale           Locale string
	 * @param layoutDefinition Definition of layout to which a field belongs
	 * @param pageUri          Uri of page to which a field belongs
	 */
	public void putFieldIndicesInCache(String[] fieldIndices, String fieldIdentifier, String locale, String layoutDefinition, String pageUri) {
		putInCache(fieldIndices, fieldIdentifier + "_" + layoutDefinition + "_" + pageUri, locale, FIELD_INDICES_SUFFIX, new String[]{FIELD_INDEXES_GROUP}, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes field indices from cache
	 */
	public void flushFieldIndices() {
		flushCacheGroup(FIELD_INDEXES_GROUP, PageContext.APPLICATION_SCOPE);
	}

	// C O N T E N T   P A G E

	/**
	 * Gets content page data from cache
	 *
	 * @param uri Uri of page to get
	 * @return Data of content page
	 */
	public ContentPageData getContentPageFromCache(String uri) {
		return (ContentPageData) getFromCache(uri, null, CONTENT_PAGE_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Puts the content page into cache
	 *
	 * @param contentPageData Data of content page
	 * @param uri Uri of page to get
	 */
	public void putContentPageInCache(ContentPageData contentPageData, String uri) {
		putInCache(contentPageData, uri, null, CONTENT_PAGE_SUFFIX, new String[]{CONTENT_PAGE_GROUP}, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Updates last modified time in cache for specified content page uri
	 *
	 * @param uri Uri of page to update its modification time
	 */
	public void updateContentPageLastModifiedInCache(String uri) {
		ContentPageData cpd = getContentPageFromCache(uri);
		if ( cpd != null ) {
			cpd.setLastModified(new Date().getTime());
			putContentPageInCache(cpd, uri);
		}
	}

    /**
     * Update roles of content page in cache
     * @param uri uri of page
     * @param roles new roles
     */
    public void updateContentPageRolesInCache(String uri, String roles) {
        ContentPageData cpd = getContentPageFromCache(uri);
        if ( cpd != null ) {
            cpd.setRoles(roles);
            putContentPageInCache(cpd, uri);
        }
    }

	/**
	 * Flushes content page from the cache
	 *
	 * @param uri Uri of the content page to flush
	 */
	public void flushContentPageCache(String uri) {
		flushCache(uri, null, CONTENT_PAGE_SUFFIX, PageContext.APPLICATION_SCOPE);
	}

	/**
	 * Flushes all content pages from cache
	 */
	public void flushContentPageCache() {
		flushCacheGroup(CONTENT_PAGE_GROUP, PageContext.APPLICATION_SCOPE);
	}


	// C O M M O N

	/**
	 * Gets content from cache from Application Scope
	 *
	 * @param compositeKey Key in the cache storage. The format is </code>pageUri_identifier</code> or </code>definitionName_identifier</code>
     *                     (other formats can be used too)
	 * @param locale       Locale string
	 * @param suffix       Used to determine scope of key: page, layout, ...
	 * @param scope        PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE
	 * @return Serializable object with content or <code>null</code> if content is not found or stale.
	 */
	public Object getFromCache(String compositeKey, String locale, String suffix, int scope) {
		if ( log.isDebugEnabled() ) {
			log.debug("Get from cache compositeKey: " + compositeKey + ", locale: " + locale + ", suffix: " + suffix);
		}

		String fullKey = admin.generateEntryKey(compositeKey, request, scope, locale, suffix);

		if ( log.isDebugEnabled() ) {
			log.debug("Get from cache, full cache key: " + fullKey);
		}

		Cache cache = admin.getCache(request, scope);
		Object content;
		try {
			content = cache.getFromCache(fullKey, refreshPeriod);
			if ( log.isDebugEnabled() ) {
				log.debug("Using cached entry for key :" + fullKey);
			}
		} catch ( NeedsRefreshException nre ) {
			if ( log.isDebugEnabled() ) {
				log.debug("Cache stale or cache scope flushed for key :" + fullKey);
			}
			cache.cancelUpdate(fullKey);
			return null;
		}
		return content;
	}

	/**
	 * Puts into cache in Application or Session scope
	 *
	 * @param content Serializable object with content
	 * @param key     Key in the cache storage.
	 *                The format is </code>pageUri_identifier</code> or </code>definitionName_identifier</code> or </code>id_identifier</code>.
     *                (Other formats can be used too)
	 * @param locale  Locale string
	 * @param suffix  Used to determine scope of key: page, layout, ...
	 * @param groups  The array of names of groups to which this cached object
     *                will participate
	 * @param scope   PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE
	 */
	public void putInCache(Object content, String key, String locale, String suffix, String[] groups, int scope) {
		if ( log.isDebugEnabled() ) {
			log.debug("Put into cache compositeKey: " + key + ", locale: " + locale + ", suffix: " + suffix);
		}
		Cache cache = admin.getCache(request, scope);
		String fullKey = admin.generateEntryKey(key, request, scope, locale, suffix);
		if ( log.isDebugEnabled() ) {
			log.debug("Put into cache, full cache key: " + fullKey);
		}
		cache.putInCache(fullKey, content, groups);
	}

	/**
	 * Flushes the entry of the cache
	 *
	 * @param key    Key in the cache storage.
	 *               The format is </code>pageUri_identifier</code> or </code>definitionName_identifier</code> or </code>id_identifier</code>.
     *               (Other formats can be used too)
	 * @param locale Locale string
	 * @param suffix Used to determine scope of key: page, layout, ...
	 * @param scope  PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE
	 */
	public void flushCache(String key, String locale, String suffix, int scope) {
		Cache cache = admin.getCache(request, scope);
		key = admin.generateEntryKey(key, request, scope, locale, suffix);
		cache.flushEntry(key);
	}

	/**
	 * Flushes the group
	 *
	 * @param group Cache group
	 * @param scope PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE
	 */
	public void flushCacheGroup(String group, int scope) {
		Cache cache = admin.getCache(request, scope);
		cache.flushGroup(group);
	}

	/**
	 * Flushes the scope
	 *
	 * @param scope PageContext.APPLICATION_SCOPE or PageContext.SESSION_SCOPE
	 */
	public void flushCacheScope(int scope) {
		admin.setFlushTime(scope);
	}

	/**
	 * Flushes all cache
	 */
	public void flushAllCache() {
		admin.flushAll();
        if (log.isInfoEnabled()) {
            log.info("All cache of web layer flushed");
        }
    }

	/**
     * A struct to store content resource contents
	 */
	public static class ResourceData implements Serializable {
		private byte[] data;
		private String mimeType = null;
		private String charset = null;
		private String roles = "";
		private long lastModified = -1;

        /**
         * Constructs a new ResourceData with specified properties and no charset
         *
         * @param array         Binary data
         * @param mimeType      Mime-type of the resource
         * @param roles         Comma-separated list of roles that are assigned
         *                      to this resource
         * @param lastModified  Last modification date/time
         */
		public ResourceData(byte[] array, String mimeType, String roles, long lastModified) {
			this(array, mimeType, null, roles, lastModified);
		}

        /**
         * Constructs a new ResourceData with specified properties and no charset
         *
         * @param array         Binary data
         * @param mimeType      Mime-type of the resource
         * @param charset       Charset that corresponds to this resource
         * @param roles         Comma-separated list of roles that are assigned
         *                      to this resource
         * @param lastModified  Last modification date/time
         */
		public ResourceData(byte[] array, String mimeType, String charset, String roles, long lastModified) {
			setData(array);
			setMimeType(mimeType);
			setRoles(roles);
			setCharset(charset);
			setLastModified(lastModified);
		}

        /**
         * Gets the binary data that is content of this resource
         *
         * @return Binary data
         */
		public byte[] getData() {
			return data;
		}

        /**
         * Sets the binary data that is content of this resource
         *
         * @param data Binary data
         */
		public void setData(byte[] data) {
			this.data = data;
		}

        /**
         * Gets mime-type of this resource
         *
         * @return Mime-type
         */
		public String getMimeType() {
			return mimeType;
		}

        /**
         * Sets mime-type of this resource
         *
         * @param mimeType Mime-type to set
         */
		public void setMimeType(String mimeType) {
			this.mimeType = mimeType;
		}

        /**
         * Gets charset that corresponds to this resource
         *
         * @return Charset
         */
		public String getCharset() {
			return charset;
		}

        /**
         * Sets charset that corresponds to this resource
         *
         * @param charset Charset to set
         */
		public void setCharset(String charset) {
			this.charset = charset;
		}

		/**
		 * Gets comma-separated list of roles that are assigned to this resource
		 *
		 * @return Comma-separated list of roles
		 */
		public String getRoles() {
			return roles;
		}

		/**
		 * Sets comma-separated list of roles
         *
         * @param roles Comma-separated list of roles
		 */
		public void setRoles(String roles) {
			this.roles = roles;
		}

        /**
         * Gets last modification date/time
         *
         * @return Last modification date/time
         */
		public long getLastModified() {
			return lastModified;
		}

        /**
         * Sets last modification date/time
         *
         * @param lastModified Last modification date/time
         */
		public void setLastModified(long lastModified) {
			this.lastModified = lastModified;
		}
	}

	/**
	 * A struct to store content page contents
	 */
	public static class ContentPageData implements Serializable {
        private Long id = null;
        private String cpDefinition = null;
		private String roles = "";
		private long lastModified = -1;
        private Long cacheMaxAge = new Long(-1);

        /**
         * Constructs a new ContentPageData with specified properties
         *
         * @param id            ID of content page
         * @param cpDefinition  Additional tiles definition name
         * @param roles         Comma-separated list of roles that are assigned
         *                      to this content page
         * @param cacheMaxAge   Maximum age that page needs to be cached at client
         */
		public ContentPageData(Long id, String cpDefinition, String roles,
                               Long cacheMaxAge) {
            setId(id);
            setCpDefinition(cpDefinition);
			setRoles(roles);
            setCacheMaxAge(cacheMaxAge);
			setLastModified(new Date().getTime());
		}

        /**
         * Returns content page ID.
         *
         * @return ID
         */
        public Long getId() {
            return id;
        }

        /**
         * Sets content page ID.
         *
         * @param id ID to set
         */
        public void setId(Long id) {
            this.id = id;
        }

        /**
         * Returns content page tiles definition name
         *
         * @return additional definition name
         */
		public String getCpDefinition() {
			return cpDefinition;
		}

        /**
         * Sets content page tiles definition name
         *
         * @param cpDefinition additional definition name
         */
		public void setCpDefinition(String cpDefinition) {
			this.cpDefinition = cpDefinition;
		}

		/**
		 * Gets comma-separated list of roles that are assigned to this page
		 *
		 * @return Comma-separated list of roles
		 */
		public String getRoles() {
			return roles;
		}

		/**
		 * Sets comma-separated list of roles that are assigned to this page
         *
         * @param roles Comma-separated list of roles
		 */
		public void setRoles(String roles) {
			this.roles = roles;
		}

        /**
         * Gets last modification date/time
         *
         * @return Last modification date/time
         */
		public long getLastModified() {
			return lastModified;
		}

        /**
         * Sets last modification date/time
         *
         * @param lastModified Last modification date/time
         */
		public void setLastModified(long lastModified) {
			this.lastModified = lastModified;
		}

        /**
         * Gets maximum age that page needs to be cached at client
         *
         * @return Max cache age
         */
        public Long getCacheMaxAge() {
            return cacheMaxAge;
        }

        /**
         * Sets maximum age that page needs to be cached at client
         *
         * @param cacheMaxAge Max cache age
         */
        public void setCacheMaxAge(Long cacheMaxAge) {
            this.cacheMaxAge = cacheMaxAge;
        }
	}

	/**
	 * A struct to store CFV contents
	 */
	public static class CFVData implements Serializable {
		private String data = null;
		private Long contentFieldId = null;
		private Long contentFieldValueId = null;
		private byte contentFieldType = 0;
        private Long ownerId = null;

        /**
         * Constructs a new CFVData with specified properties
         *
         * @param data                  Data that is stored in this CTF
         * @param contentFieldId        ID of the content field
         * @param contentFieldType      Type of the content field
         * @param contentFieldValueId   ID of this CTF
         * @param ownerId               ID of owner
         */
		public CFVData(String data, Long contentFieldId, byte contentFieldType,
                       Long contentFieldValueId, Long ownerId) {
			setData(data);
			setContentFieldType(contentFieldType);
			setContentFieldId(contentFieldId);
			setContentFieldValueId(contentFieldValueId);
            setOwnerId(ownerId);
        }

        /**
         * Gets data of this CTF
         *
         * @return Data
         */
		public String getData() {
			return data;
		}

        /**
         * Sets data of this CTF
         *
         * @param data Data to set
         */
		public void setData(String data) {
			this.data = data;
		}

        /**
         * Gets content field ID
         *
         * @return Content field ID
         */
		public Long getContentFieldId() {
			return contentFieldId;
		}

        /**
         * Sets content field ID
         *
         * @param contentFieldId Content field ID to set
         */
		public void setContentFieldId(Long contentFieldId) {
			this.contentFieldId = contentFieldId;
		}

        /**
         * Gets ID of this CFV
         *
         * @return CFV ID
         */
		public Long getContentFieldValueId() {
			return contentFieldValueId;
		}

        /**
         * Sets ID of this CFV
         *
         * @param contentFieldValueId CFV ID to set
         */
		public void setContentFieldValueId(Long contentFieldValueId) {
			this.contentFieldValueId = contentFieldValueId;
		}

        /**
         * Gets content field type
         *
         * @return Content field type
         */
		public byte getContentFieldType() {
			return contentFieldType;
		}

        /**
         * Sets content field type
         *
         * @param contentFieldType Content field type to set
         */
		public void setContentFieldType(byte contentFieldType) {
			this.contentFieldType = contentFieldType;
		}

        /**
         * Returns owner ID.
         *
         * @return owner ID
         */
        public Long getOwnerId() {
            return ownerId;
        }

        /**
         * Sets owner ID.
         *
         * @param ownerId ID to set
         */
        public void setOwnerId(Long ownerId) {
            this.ownerId = ownerId;
        }
    }

	/**
	 * A struct to store sequence contents
	 */
	public static class SequenceData implements Serializable {
        /**
         * Sequence ID
         */
        private Long id = null;

        /**
         * Constructs a new SequenceData with specified properties
         *
         * @param id        ID of sequence
         */
		public SequenceData(Long id) {
            setId(id);
        }

        /**
         * Returns sequence ID.
         *
         * @return sequence ID
         */
        public Long getId() {
            return id;
        }

        /**
         * Sets sequence ID.
         *
         * @param id ID to set
         */
        public void setId(Long id) {
            this.id = id;
        }
    }
}
